package com.zookeeper;

import org.apache.zookeeper.*;
import org.apache.zookeeper.data.Stat;
import org.junit.Test;

import javax.sound.midi.Soundbank;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.CountDownLatch;

//当实际生产中的某一个资源需要控制客户端同步访问时，就会使用分布式锁，每个客户端都会先访问zookeeper来控制同步
//然后在指定的节点中创建有序的临时子节点，当获取锁时，会先判断自己监视的前面的那个临时节是否存在（每个节点都会监视自己前面的那一个节点）
// 如果不存在，说明自己是在最前面，那么就会调用自己的业务方法
public class MyLock implements Watcher {
    private final String CONNECT_ADDRESS = "192.168.1.120";
    private final Integer SESSIONT_IMEOUT = 5 * 1000;
    private static ZooKeeper zk;
    private final String LOCK_ROOT_PATH="/lock";
    private static String LOCK_NODE_PATH="lock_";
    private static String lockPath;
    private CountDownLatch countDownLatch = new CountDownLatch(1);

    public MyLock() {
        try {
            zk=new ZooKeeper(CONNECT_ADDRESS,SESSIONT_IMEOUT,this);
        }catch (Exception e){
            e.printStackTrace();
        }
    }

    public void process(WatchedEvent event) {
        if (Event.KeeperState.SyncConnected==event.getState()){
            if (Event.EventType.None==event.getType())
            countDownLatch.countDown();
        }
        if (Event.EventType.NodeDeleted==event.getType()){
            synchronized (this){
                this.notifyAll();
            }
        }
    }
    
    public void acquireLock(){
        //创建锁
        createLock();
        //尝试获取锁
        getLock();
    }

    private void createLock() {
        try {
            countDownLatch.await();
            Stat stat=zk.exists(LOCK_ROOT_PATH,false);
            if (stat==null){//判断根节点是否存在
                zk.create(LOCK_ROOT_PATH,new byte[0], ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
            }
            //创建子节点（实际生产中每一个客户端都会创建一个属于自己的临时节点）
            lockPath=zk.create(LOCK_ROOT_PATH+"/"+LOCK_NODE_PATH,new byte[0], ZooDefs.Ids.OPEN_ACL_UNSAFE,CreateMode.EPHEMERAL_SEQUENTIAL);
            System.out.println(lockPath);
        }catch (Exception e){
            e.printStackTrace();
        }
    }

    public void getLock() {
        try {//遍历获取子节点路径，并进行排序
           List<String> childrenPath=zk.getChildren(LOCK_ROOT_PATH,false);
           //给list集合排序(应该是会按照一定的规则进行排序)
           Collections.sort(childrenPath);
           //返回lockPath节点路径在list集合中的索引(就是存放在list集合中的第i个位置list[i])
           //int index=childrenPath.indexOf(lockPath.substring(LOCK_ROOT_PATH.length()+1));
           int index=childrenPath.indexOf(lockPath);//我感觉应该这样写
           if (index==0){
               //这是第一个节点前面肯定不会有节点，第一个节点不需要监视（因为自己就在最前面）
               //如果是实际应用中说明已经获取到锁了，这里面就可以写上相应的业务代码
               //System.out.println("获取锁成功，启动业务方法");
               return;
           }else {
               String path=childrenPath.get(index-1);
               //判断前一个节点是否存在
               Stat stat=zk.exists("/"+path,this);
               //如果不存在说明自己就是在最前面，那么自己就获得了锁，就可以执行自己的业务代码(好像不能这么写)
               //如果不存在说明自己就是在最前面，那么重新调本方法，遍历节点，自己就会在集合中的第一个位置
               if (stat==null){
                   //System.out.println("获取锁成功，启动业务方法");
                   getLock();//重新获取锁
               }else{
                   synchronized (this){
                       this.wait();
                   }
                   getLock();
               }
           }
        }catch (Exception e){
            e.printStackTrace();
        }
    }

    public void closeLock(){
        try {
            //zk.delete(lockPath,-1);//我感觉不需要删除，因为是临时节点，客户端关闭就会自动删除
            zk.close();
            //System.out.println("节点已经删除，锁已经释放");//不是临时节点吗为啥还需要删除
        }catch (Exception e){
            e.printStackTrace();
        }
    }

    @Test
    public void test(){
        MyLock myLock=new MyLock();
        myLock.acquireLock();
    }
}
